/* 
 * File:   bench.cpp
 * Author: ljr
 *
 * Created on 03 February 2010, 16:22
 */

#include "bench.h"

#include <math.h>
#include <stdint.h>

void print_message(string message) {
	//    return;
	cout << message << endl;
}

void free_pointers() {
	delete raid_controller;
	raid.clear();
}

/* Create a new payload for the token */
token_payload * new_token_payload(unsigned char _unit, unsigned char _file_size,
		unsigned char _io_operation) {
	struct token_payload *tmp = new (struct token_payload);

	tmp->unit = _unit;
	tmp->file_size = _file_size;
	tmp->io_operation = _io_operation;

	return tmp;
};

/* Delete a payload */
inline void delete_token_payload(void* _token_payload) {
	delete (struct token_payload *) _token_payload;
};

/* Return a casted pointer to Pbox field of a Token */
//      E.g :
//          tk_pBox(customer)->unit
//      instead of
//          ((token_payload*) customer.Pbox())->unit

inline token_payload* get_payload(Token tk) {
	return ((token_payload*) tk.Pbox());
};

void mrip2_initialize(int* argc, char*** argv) {
	rank = startCommunication(*argc, *argv);
	//Inicializa_BatchMeansAnalyser();
	Inicializa_SpectralParameterAnalyser();
	receiveInt2(&seed_from_master);
	Initialize_CMRRandomGenerator(seed_from_master);
}

int main(int argc, char** argv) {

	cout << setprecision(10);

	// initialization
	atexit(free_pointers);
	parse_arguments(argc, argv);
	create_facilities();


	// mrip
	mrip2_initialize(&argc, &argv);
	initialize_random_engine(seed_from_master);


	// processing
	new Future(LINKED);

	Token customer(1);
	Future::Schedule(BEGIN_TOUR, 0.0, customer);

	//while (Future::SimTime() < TIME_LIMIT) {
	while (1) {
		number_of_interations++;
		//print_message("begin while.");
		Estatus es = Future::NextEvent();
		switch (es.event_id) {
			case BEGIN_TOUR: begin_tour();
				break;
			case REQUEST_CONTROLLER: request_controller();
				break;
			case FORWARD_UNIT: forward_to_unit();
				break;
			case REQUEST_DISK: request_disk();
				break;
			case RELEASE_DISK: done_end_tour();
				//ostringstream msg;
				//msg << "process observation. throughput " << throughput;
				//print_message(msg.str().c_str());
				//print_message("process observation\n");
				//printf("t = %f and interation = %d\n", throughput, number_of_interations);
				// decide whether stop simulation or not.

				//printf("Iteration = %ld\n", operations_done);
				//ProcessObservation_SA(throughput + normal(10, 10240));
				ProcessObservation_SA(throughput);
				break;
			default: cout << "PULLED SOMETHING UNEXPECTED OUT OF THE HAT!\n";
		}
	}

	Future::ReportStats();

	return (EXIT_SUCCESS);
}

template<typename T>
void parse_parameter(const char* argv, T array[], T* variable, int inf, int sup,
		string message) {
	//    print_message("parse parameter.");
	int parameter;

	//cout << argv << endl;
	parameter = atoi(argv);
	if (parameter < inf || parameter > sup) {
		ErrXit(1, message.c_str());
	}
	*variable = array[parameter];
}

void parse_arguments(int argc, char** argv) {
	//print_message("parse arguments.");
	if (argc != 5 && argc != 6) {
		ErrXit(1, "Parameters out of range");
	}

	parse_parameter<raid_levels > (argv[1], levels, &level, 0, 5,
			"Raid level out of range. Must be between 0-5");
	parse_parameter<bunch_of_disks > (argv[2], disks, &number_of_disks, 0, 2,
			"Number os disks out of rande. Must be between 0-2");
	parse_parameter<file_sizes > (argv[3], files, &size_of_files, 0, 2,
			"File sizes out of rande. Must be between 0-2");
	parse_parameter<operation_values > (argv[4], operations, &io_operations, 0,
			2, "I/O operations out of rande. Must be between 0-2");

	if (argc == 6) {
		initialize_random_engine(atoi(argv[5]));
		//cout << "seed set to " << argv[5] << endl;
	}

	/*cout << "level = " << level << endl <<
			"disks = " << number_of_disks << endl <<
			"size of files = " << size_of_files << endl <<
			"io operations = " << io_operations << endl;*/
}

void create_facilities() {
	//print_message("create facilities.");
	raid_controller = new Facility("raid_controller");

	for (int i = 0; i < number_of_disks; i++) {
		stringstream strm;
		strm << "disk" << i;
		//cout << "creating " << strm.str() << endl;
		raid.push_back(new Facility(strm.str().c_str()));
	}

	//    cout << "Vector created." << endl;
}

void initialize_random_engine(long _seed) {
	//print_message("initialize random engine.");
	// stream 1 for the controller
	seed(_seed, 1);

	// stream 2 for the size of the files
	seed(random(1024, 65535), 2);

	// stream 3 for the type of the operation
	seed(random(1024, 65535), 3);

	// stream from 4 to 11 at most for the disks
	for (int i = 0; i < number_of_disks; i++) {
		seed(random(1024, 65535), i + 4);
	}

	// stream for the multiplication factor in the big_o function
	seed(random(1024, 65535), 15);
}

bool rand_quarter() {
	return (random(1, 65535) % 40) % 4 == 0;
}

bool rand_mean() {
	return random(1, 65536) % 2 == 0;
}

unsigned char get_file_size() {
	//print_message("get file size.");
	stream(2);
	switch (size_of_files) {
		case SMALL25_BIG75:
			return (rand_quarter())
					? random(1, 100)
					: random(101, 200);
		case SMALL50_BIG50:
			return (rand_mean())
					? random(1, 100)
					: random(101, 200);
		case SMALL75_BIG25:
			return (!rand_quarter())
					? random(1, 100)
					: random(101, 200);
	};

}

unsigned char get_io_operation() {
	//print_message("get io operation.");
	// returns:
	// ZERO for writing
	// ONE for reading

	stream(3);
	switch (size_of_files) {
		case WRITE25_READ75:
			return (rand_quarter())
					? 0
					: 1;
		case WRITE50_READ50:
			return (rand_mean())
					? 0
					: 1;
		case WRITE75_READ25:
			return (!rand_quarter())
					? 0
					: 1;
	};
}

/*=============================================================================
-----------------------------------------------------------------------------*/
void begin_tour(void) {
	//print_message("begin tour.");
	// select the stream of the controller
	stream(1);

	Token customer = Future::CurrentToken();
	Future::UpdateArrivals();

	Future::Schedule(REQUEST_CONTROLLER, 0.0, customer);

	// spawn the next operation request...
	customer.Id(customer.Id() + 1);
	Future::Schedule(BEGIN_TOUR, expntl(MEAN_ARRIVAL_TIME), customer);
}

/*=============================================================================
-----------------------------------------------------------------------------*/

inline unsigned char get_unit() {
	//print_message("get unit.");
	return robin++ % number_of_disks;
}

void request_controller(void) {
	//print_message("request controller.");
	Token customer = Future::CurrentToken();
	if (raid_controller->Request(customer) == FREE) {
		customer.SetPbox(new_token_payload(
				get_unit(),
				get_file_size(), // this will change the current stream of random
				get_io_operation())); // this as well.

		// we're back in the controller's stream of random now...
		stream(1);
		Future::Schedule(FORWARD_UNIT, FORWARD_OVERHEAD, customer);
	}
}

/*=============================================================================
-----------------------------------------------------------------------------*/
// FIXME: rearrange the code for simulate synchronous disks when using
//      levels 2 and 3.

void forward_to_unit(void) {
	//print_message("forward to unit.");
	Token customer = Future::CurrentToken();
	raid_controller->Release(customer.Id());
	Future::Schedule(REQUEST_DISK, 0.0, customer);
}

/*=============================================================================
-----------------------------------------------------------------------------*/

/**
 * In order to get different size of simulations, set the conditions of the for's
 * like this:
 *
 * - small one: for (... i < (5 * 100) ...) ... for (... i < (5 * 100) ...) ...
 *
 * - medium one: for (... i < (25 * 100) ...) ... for (... i < (10 * 100) ...) ...
 *
 */
void big_o() {

	// set the length of the simulation. Uncomment one of them.
#define THIN
//#define SMALL
//#define MEDIUM

#ifdef THIN
	return;
#endif

	stream(15);
	int j = 0;

#ifdef SMALL
	for (int i = 0; i < (5 * 100); i++) {
		for (int k = 0; k < (5 * 50); k++) {
#else
	for (int i = 0; i < (25 * 100); i++) {
		for (int k = 0; k < (10 * 100); k++) {
#endif
			j += i * random(1, 65535) * k;
		}
	}
}

double process_load(int id, unsigned char io, int size) {
	//print_message("process load.");
	big_o();
	return (*process_level[level])(id, io, size);
}

void request_disk(void) {
	//print_message("request disk.");
	Token customer = Future::CurrentToken();
	if (raid[get_payload(customer)->unit]->Request(customer, 0) == FREE) {
		// invoke the proper function to process the choosen raid level.
		// in case of doubt, see `function pointer' in the manual of your compiler
		struct token_payload* payload = get_payload(customer);
		// this will change the current stream of random
		double time = process_load(customer.Id(), payload->io_operation,
				payload->file_size);

		// we're back in the controller's stream of random now...
		stream(1);
		Future::Schedule(RELEASE_DISK, time, customer);
	}
}

/*=============================================================================
-----------------------------------------------------------------------------*/

void done_end_tour(void) {
	//print_message("done end tour.");
	Token customer = Future::CurrentToken();
	raid[get_payload(customer)->unit]->Release(customer.Id());
	Future::UpdateDepartures();

	// calculate the metric!
	throughput = operations_done++ / Future::SimTime();
	//cout << "t = " << throughput << endl;

	// Clean up the room
	delete_token_payload(customer.Pbox());
}

void log_message(int token_id, unsigned char io, int size) {
	return;
	cout << "Processing token " << token_id << " operating " <<
			((io == 0) ? "W " : "R ") << "with size " << size << endl;
}

double process_l0(int token_id, unsigned char io, int size) {
	log_message(token_id, io, size);

	switch (io) {
		case 0: // writing
			return size * WRITING_SPEED;
			break;
		case 1: // reading
			return size * READING_SPEED;
			break;
		default:
			ostringstream msg;
			msg << "Wrong I/O operation when processing token " << token_id;
			ErrXit(EXIT_FAILURE, msg.str().c_str());
	};

	return 0.0; // pacify compiler... this never will happen...
}

double process_l1(int token_id, unsigned char io, int size) {
	double result = process_l0(token_id, io, size);

	if (io == 0) { // write twice!!
		return result * 2;
	}
	return result;
}

double process_l2(int token_id, unsigned char io, int size) {
	log_message(token_id, io, size);

	// assuming 1 disk for the harming...
	unsigned char bites = number_of_disks - 1;
	double calc_overhead = ((READING_SPEED / bites) * 0.06);
	double sync_overhead = READING_SPEED * 0.01;

	switch (io) {
		case 0: // writing
			return size * (WRITING_SPEED / bites) + calc_overhead +
					sync_overhead;
			break;
		case 1: // reading
			return size * (READING_SPEED / bites) + sync_overhead;
			break;
		default:
			ostringstream msg;
			msg << "Wrong I/O operation when processing token " << token_id;
			ErrXit(EXIT_FAILURE, msg.str().c_str());
	};

	return 0.0; // pacify compiler... this never will happen...
}

double process_l3(int token_id, unsigned char io, int size) {
	log_message(token_id, io, size);

	unsigned char bites = number_of_disks - 1;
	double calc_overhead = ((READING_SPEED / bites) * 0.02);
	double sync_overhead = READING_SPEED * 0.01;

	switch (io) {
		case 0: // writing
			return size * (WRITING_SPEED / bites) + calc_overhead +
					sync_overhead;
			break;
		case 1: // reading
			return size * (READING_SPEED / bites) + sync_overhead;
			break;
		default:
			ostringstream msg;
			msg << "Wrong I/O operation when processing token " << token_id;
			ErrXit(EXIT_FAILURE, msg.str().c_str());
	};

	return 0.0; // pacify compiler... this never will happen...
}

double process_l4(int token_id, unsigned char io, int size) {
	log_message(token_id, io, size);

	double calc_overhead = READING_SPEED * 0.04;

	switch (io) {
		case 0: // writing
			return size * WRITING_SPEED + calc_overhead;
			break;
		case 1: // reading
			return size * READING_SPEED;
			break;
		default:
			ostringstream msg;
			msg << "Wrong I/O operation when processing token " << token_id;
			ErrXit(EXIT_FAILURE, msg.str().c_str());
	};

	return 0.0; // pacify compiler... this never will happen...

}

double process_l5(int token_id, unsigned char io, int size) {
	log_message(token_id, io, size);

	double get_other_disk_overhead = READING_SPEED * 0.09;

	return process_l4(token_id, io, size) + get_other_disk_overhead;
}


